#include <iostream>
#include <cmath>
#include <algorithm>
#include <functional>
#include <numeric>

#include <boost/foreach.hpp>

#include "page.h"
#include "page_db.h"
#include "junix.h"
#include "idxutils.h"
#include "vector_space_indexer.h"

namespace jsearch
{

struct vector_space_indexer::Impl
{
	public:
		Impl( std::tr1::shared_ptr<page_db>& db,
			  std::string disk_db_name ) 
			:disk_db(disk_db_name)
		{
			if (disk_db_name.empty()) {
				fprintf(stderr,"disk_db name is empty\n");
				disk_db="rindex.db";
			}

			pdb = db;   
		}

		bool build_index()
		{
			build_pages(); 
			copy_pages_to_index();
			calculate_weight();

			return true;
		}

		bool build_pages()
		{
			pages.clear();

			if (pdb->empty()) {
				fprintf(stderr,"error:page db is empty.\n");
				return false;
			}

			int sz=pdb->size();
			for (int i=0;i<sz; ++i) {
				page pg(i,pdb);
				if (!pg.build_page_index())	{
					fprintf(stderr, "error:page index building go wrong\n");
					return false;
				}

				pages.push_back(pg);
			}

			return true;
		}

		bool copy_pages_to_index()
		{
			if (empty())
				return true;

			BOOST_FOREACH(page& p, pages) {
				int id=p.id();

				typedef std::map<std::string,term_info>::const_iterator CITER;
				CITER pos = p.terms().begin();
				CITER end = p.terms().end();
				for ( ;pos!=end; ++pos) {
					index[pos->first].posting_list.push_back(
							item(id, pos->second.freq) );
				}
			}

			return true;
		}


		bool calculate_weight()
		{
			double idf;
			double N=static_cast<double>( pages.size() );
			if(!N) {
				fprintf(stderr,"error:pages is empty\n");
				return false;
			}

			BOOST_FOREACH(INDEX_V& iv, index) {
				double n = static_cast<double>(iv.second.posting_list.size() );
				idf = log10(static_cast<double>(N/n));
				iv.second.idf = idf;

				BOOST_FOREACH(item& i,iv.second.posting_list) {
					i.weight *= idf;
				}
			}

			return true;
		}

		bool calculate_idf()
		{
			INDEX::iterator pos=index.begin();
			double N=static_cast<double>( pages.size() );
			if (!N)	{
				fprintf(stderr,"error:pages is empty\n");
				return false;
			}

			BOOST_FOREACH(INDEX_V& iv,index) {
				double n = static_cast<double>(iv.second.posting_list.size());
				iv.second.idf = log10(static_cast<double>(N/n));
			}

			return true;
		}

		bool persist()
		{
			int fd = open(disk_db.c_str(), O_RDWR|O_CREAT|O_TRUNC, 0644);
			if (fd==-1)
				err_sys("can not create index db file"); 

			int header_zone_size = 
				index.size()*sizeof(disk_index_header) + sizeof(int);

			writen(fd, (char*)&header_zone_size, sizeof(int));
			disk_index_header dheader;
			disk_index_data ddata;

			int header_size = sizeof(disk_index_header);
			int data_size = sizeof(disk_index_data);

			int hpos = sizeof(int);
			int dpos = header_zone_size;

			INDEX::iterator pos=index.begin();
			INDEX::iterator end=index.end();
			for ( ;pos!=end; ++pos)
			{
				memset((char*)&dheader, 0, header_size);
				strncpy(dheader.name,
						pos->first.c_str(),
						sizeof(dheader.name));
				dheader.name[sizeof(dheader.name)-1]='\0';

				dheader.size = pos->second.posting_list.size()*data_size;
				dheader.offset = dpos;
				dheader.idf = pos->second.idf;

				if (lseek(fd,hpos,SEEK_SET) == -1)
					err_sys("lseek error");

				writen(fd, (char*)&dheader, header_size);
				hpos += header_size;


				if (lseek(fd,dpos,SEEK_SET) == -1)
					err_sys("lseek error");

				std::vector<item>::iterator iter = 
					pos->second.posting_list.begin();
				for ( ; iter != pos->second.posting_list.end(); ++iter)
				{
					memset((char*)&ddata,0,data_size);
					ddata.page_id = iter->page_id;
					ddata.weight = iter->weight;
					writen(fd, (char*)&ddata, data_size);
				}

				dpos += dheader.size;
			}

			if (close(fd))
				err_sys("close");

			return true; 
		}

		void print( std::ostream& os= std::cout )
		{
			BOOST_FOREACH(INDEX_V& iv, index) {
				BOOST_FOREACH(item& it,iv.second.posting_list) {
					os<<iv.first<<" "<<it.page_id<<" "
						<<it.weight<<std::endl;
				}
			}

			os.clear();
		}

		int size() const
	   	{ 
			return pages.size();	
		}

		bool empty() const
		{	
			return pages.empty();
		}

	private:
		INDEX index;/*indexed data in memory*/
		std::string disk_db;/*where the indexed data should be stored*/
		std::vector<page> pages;/*data structure per page(document) */
		std::tr1::shared_ptr<page_db> pdb;/* page(document) db*/

};


/******************************************************************/
//    vector_space_index member function
/******************************************************************/

vector_space_indexer::vector_space_indexer( std::tr1::shared_ptr<page_db>& db,
			                                std::string d_db)
			:pimpl( new Impl(db,d_db))
{ }

/**
 * This is the main indexing process.
 * Before it, pages should be build.
 */

bool vector_space_indexer::build_index()
{
	return pimpl->build_index();
}


void vector_space_indexer::print( std::ostream& os)
{
	pimpl->print(os);
}

bool vector_space_indexer::persist()
{
	return pimpl->persist();
}

int vector_space_indexer::size()
{
	return pimpl->size();
}
bool vector_space_indexer::empty()
{
	return pimpl->empty();
}

//----------------------------
// construct of class item
//
item::item(int id, double wt)
:page_id(id),weight(wt)
{
	if (id<0) {
		std::cerr<<"error:id is invalid"<<std::endl;
		exit(1);
	}
}		

}//end namespace
